fix(extension): use offscreen document for persistent WebSocket connection#569
fix(extension): use offscreen document for persistent WebSocket connection#569lyfuci wants to merge 3 commits intojackwener:mainfrom
Conversation
…ction Chrome MV3 Service Workers are suspended by the browser when idle, causing the WebSocket connection to the daemon to drop silently. The existing keepalive alarm re-connects after wake, but there is a window where commands fail and the CLI reports 'not connected'. Fix: move the WebSocket into a chrome.offscreen document whose lifetime is tied to the browser window, not the Service Worker. The SW becomes a thin message-relay layer; the offscreen document owns the connection. Changes: - extension/src/offscreen.ts (new): WebSocket host with auto-reconnect - extension/offscreen.html (new): offscreen document entry point - extension/src/background.ts: delegate WS ops to offscreen via messages - extension/manifest.json: add 'offscreen' permission - extension/vite.config.ts: add offscreen as a build entry Tested: extension stays connected after >2h with Chrome idle / SW suspended on Linux.
Astro-Han
left a comment
There was a problem hiding this comment.
Clean architecture — offscreen document for persistent WS is the right call for MV3, and the legacy fallback path is a nice safety net. Two minor observations:
-
pendingFramesin offscreen.ts has no cap. If the SW stays unreachable for a while the array could grow unbounded. A simple max (e.g. 100) with oldest-first eviction would be a cheap safeguard. -
forceLegacyTransportis set permanently on firstensureOffscreen()failure. Might be worth resetting it periodically (e.g. on keepalive alarm) so a transient error doesn't lock out offscreen for the entire session.
|
Follow-up to my earlier review: I think there is one more reliability gap in the offscreen transport. In That means "accepted by the Service Worker" is currently being treated as "fully delivered", but those are not the same thing. If the SW is interrupted after the early ack but before So this still leaves a command-loss window exactly in the path this PR is trying to harden. I would suggest either:
There is a second loss path on the return leg as well. In That means the current design can lose commands in both directions: once on the early ack path, and again on the result-send path. I think the response payload needs the same durability treatment as incoming frames, not just a reconnect side effect. |
Problem
Chrome MV3 Service Workers are suspended by the browser after a short idle period (~30s). When this happens, the WebSocket connection to the opencli daemon drops silently. Although a
keepalivealarm fires every ~24s to wake the SW and callconnect(), there is a gap window where:Users experience this as the extension randomly disconnecting, requiring them to manually click the extension icon to wake it.
Root Cause
The WebSocket connection lives inside the Service Worker. Chrome's MV3 Service Worker lifecycle does not support persistent connections — it is designed to be ephemeral.
Fix
Move the WebSocket connection into a
chrome.offscreendocument. Offscreen documents have a stable lifetime tied to the browser window (not the SW), so the connection survives SW sleep/wake cycles.The Service Worker becomes a thin relay layer:
chrome.runtime.sendMessageChanges
extension/src/offscreen.tsextension/offscreen.htmlextension/src/background.tsextension/manifest.jsonoffscreenpermissionextension/vite.config.tsoffscreenbuild entryTesting
Tested on Linux (Chrome 144) with the browser idle for 2+ hours. Extension remained connected throughout — no manual reconnection required.
Previously this would show
[MISSING] Extension: not connectedafter Chrome suspended the SW.